Skip to content

BUGFIX: template-require-iframe-title — flag title={{null|undefined|number}}#2731

Draft
johanrd wants to merge 7 commits intoember-cli:masterfrom
johanrd:fix/iframe-title-value-checks
Draft

BUGFIX: template-require-iframe-title — flag title={{null|undefined|number}}#2731
johanrd wants to merge 7 commits intoember-cli:masterfrom
johanrd:fix/iframe-title-value-checks

Conversation

@johanrd
Copy link
Copy Markdown
Contributor

@johanrd johanrd commented Apr 21, 2026

Summary

  • Premise 1: <iframe> elements need an accessible name so assistive technology can convey their content. The normative source is WCAG SC 4.1.2 Name, Role, Value; WCAG Technique H64 is a sufficient technique illustrating that title is one way to meet it (not itself normative). Per ACCNAME 1.2 §4.3.2 step 2I (Tooltip), a non-string or empty title produces no accessible name. Mustache literals like {{null}}, {{undefined}}, or {{42}} are not strings — they can't satisfy step 2I.
  • Premise 2: Today our rule only rejects title={{false}}. title={{null}}, title={{undefined}}, and title={{42}} silently pass.
  • Conclusion: Treat GlimmerBooleanLiteral, GlimmerNullLiteral, GlimmerUndefinedLiteral, and GlimmerNumberLiteral as invalid title values in both GlimmerMustacheStatement and single-part GlimmerConcatStatement positions. These are correctness checks — a non-string literal cannot be a string-valued accessible name, regardless of framework behavior.

Fix: extract isInvalidTitleLiteral() covering the four literal AST types above.

Six new invalid tests (3 literal types × 2 syntax forms: title={{x}} vs title="{{x}}").

Whitespace-only title — now opt-out via schema option

The rule previously flagged title=" " (whitespace-only static) as a hard error. This is stricter than ACCNAME — step 2I (Tooltip) does not whitespace-trim like step 2D (aria-label) does, so a 3-space accessible name is technically assigned. The check remains on by default as an authoring-hygiene guard (useless in practice), but teams that want spec-aligned behavior can opt out via the new allowWhitespaceOnlyTitle: true option. Empty-string title="" and the non-string literal cases above are not affected by this option — they are always flagged as correctness.

Prior art

Peers diverge materially on empty/non-string title handling. Don't assume parity:

Plugin Rule Verified behavior
jsx-a11y iframe-has-title Check is if (title && typeof title === 'string') → bail. Flags title="" (falsy), flags title={42} (not a string), flags title={null} (falsy). Does NOT flag title=" " (truthy string).
vuejs-accessibility iframe-has-title Check is `title === null
lit-a11y iframe-title Existence-only at line 38: `!element.attribs.title
@angular-eslint/template No iframe-title equivalent in the rules directory.

Audit fixture

Translated peer-plugin test fixture at tests/audit/iframe-title/peer-parity.js. Not wired into the default test run.

johanrd added 2 commits April 21, 2026 07:52
Before: the rule only rejected title={{false}} among mustache literals.
title={{null}}, title={{undefined}}, and title={{42}} (any non-string
literal) were silently accepted even though none produces a useful
accessible name for an <iframe>.

Fix: extract isInvalidTitleLiteral(); treat boolean, null, undefined,
and numeric literals as invalid title values in both GlimmerMustacheStatement
and single-part GlimmerConcatStatement positions.

Six new invalid tests (3 literal types × 2 syntax forms).

Note: whitespace-only title values (e.g. title="   ") remain flagged —
the audit suggested upstream jsx-a11y accepts these, but an all-whitespace
title provides no accessible name, so our stricter behavior is kept.
Translates 33 cases from peer-plugin rules:
  - jsx-a11y iframe-has-title
  - vuejs-accessibility iframe-has-title
  - lit-a11y iframe-title

Fixture documents parity after this fix:
  - title={{null|undefined|number}} and concat `title="{{null}}"`
    / `title="{{42}}"` are now flagged via dynamicFalseTitle.

Remaining divergences (title={{""}} still accepted, aria-hidden /
hidden exemption, whitespace-only title over-flagged, duplicate-title
detection inherited from ember-template-lint) are annotated inline.
johanrd added 2 commits April 22, 2026 10:50
…ck into opt-out option

Two changes, both addressing fallacies flagged in review-pr-bodies.md:

1. Document that SC 4.1.2 (Name, Role, Value) is the normative requirement;
   H64 is a sufficient technique illustrating one way to meet it. Earlier
   framing attributed the requirement to H64 itself — same pattern as
   template-heading-level's miscited G141.

2. Whitespace-only `title="   "` is technically spec-compliant: ACCNAME 1.2
   step 2I (Tooltip) does not whitespace-trim like step 2D (aria-label)
   does. Keeping the strict flag as default (authoring hygiene — 3 spaces
   is useless as an accessible name in practice) but exposing
   `allowWhitespaceOnlyTitle: boolean` (default false) so teams that want
   spec-aligned behavior can opt out. Empty-string `title=""` and invalid
   mustache literals (null/undefined/number/boolean) remain always flagged:
   those are correctness, not style.
- docs/rules/template-require-iframe-title.md: wrap the config example
  in `module.exports = {...}` so eslint can parse it as a standalone
  module (bare object literals are parsed as block statements, which
  trips 'missing semicolon' on the inner object).
- tests/lib/rules/template-require-iframe-title.js: move `output`
  before `options` on the allowWhitespaceOnlyTitle test case to match
  eslint-plugin/test-case-property-ordering's [code, output, options,
  errors] sequence.
- Prettier reflow of the config example (single-line rule array)
  applied via `prettier --write`.
johanrd added 3 commits April 22, 2026 14:21
…efactor (Copilot review)

- messageId `dynamicFalseTitle` renamed to `invalidTitleLiteral` — the rule
  flags booleans, null, undefined, AND numbers, not just `false`.
- Message text generalized and now includes the literal type (boolean/null/
  undefined/number) via a {{literalType}} interpolation.
- Comment at rule top no longer claims `true` coerces to an empty-ish string
  (it doesn't — String(true) === "true"); reworded to explain that non-string
  literals don't describe frame content even when they would coerce.
- Comment inside empty-string branch reworded — "not a string" was wrong.
- isInvalidTitleLiteral() replaced with a Set-backed lookup
  (INVALID_LITERAL_TYPES) for cleaner membership checks.
- Test assertions for the renamed messageId updated (unit + audit fixture);
  hbs-tester text assertions updated for the new message format.
@johanrd johanrd force-pushed the fix/iframe-title-value-checks branch from 06219b7 to 1fd8b3b Compare April 22, 2026 17:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant